package fr.paris.sud.com;
import java.awt.Color;
import java.awt.event.KeyEvent;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.swing.ImageIcon;
import javax.swing.JPanel;

import fr.lri.swingstates.animations.Animation;
import fr.lri.swingstates.animations.AnimationRotateBy;
import fr.lri.swingstates.animations.AnimationScaleTo;
import fr.lri.swingstates.applets.BasicApplet;
import fr.lri.swingstates.canvas.CElement;
import fr.lri.swingstates.canvas.CEllipse;
import fr.lri.swingstates.canvas.CShape;
import fr.lri.swingstates.canvas.CStateMachine;
import fr.lri.swingstates.canvas.Canvas;
import fr.lri.swingstates.canvas.CStateMachine.DragOnTag;
import fr.lri.swingstates.canvas.CStateMachine.EnterOnTag;
import fr.lri.swingstates.canvas.CStateMachine.LeaveOnTag;
import fr.lri.swingstates.canvas.CStateMachine.PressOnTag;
import fr.lri.swingstates.events.*;
import fr.lri.swingstates.sm.State;
import fr.lri.swingstates.sm.StateMachine;
import fr.lri.swingstates.sm.Transition;
import fr.lri.swingstates.sm.transitions.Drag;
import fr.lri.swingstates.sm.transitions.Enter;
import fr.lri.swingstates.sm.transitions.Event;
import fr.lri.swingstates.sm.transitions.KeyType;
import fr.lri.swingstates.sm.transitions.Press;
import fr.lri.swingstates.sm.transitions.Release;
import fr.lri.swingstates.sm.transitions.TimeOut;
import fr.paris.sud.com.GridNode;
import fr.paris.sud.com.ItemAway;
import fr.paris.sud.com.ItemGhost;
import fr.paris.sud.com.VirtualGrid;

public class BoomerangStateMachine extends JPanel{

	StateMachine sm;

	private final static int WIDTH = 1200;
	private final static int HEIGHT = 800;
	private final static int MIN_SPEED = 4;
	//private Canvas canvas;
	private int lastX, lastY, secondLastX, secondLastY = -1;
	private long tick = System.currentTimeMillis();
	private double mouseSpeed = 0;
	protected CStateMachine throwInteraction;
	protected CStateMachine catchInteraction;
	protected CStateMachine gridSnapInteraction;
	private HashMap<Integer, ItemAway> itensAway = new HashMap<Integer, ItemAway>();
	private HashMap<Integer, ItemGhost> itensGhosts = new HashMap<Integer, ItemGhost>();
	private CElement commomElement = null; //communication shared element between both the machines
	public VirtualGrid fileSystem;
	public int gridIndexDraggingObject = 0;
	public int gridIndexDiplayedImage = 0;

	public void createStateMachines() {
		// CStateMachine is a subclass of StateMachine that provides 
		// events to monitor a Canvas widget 
		throwInteraction = new CStateMachine() {

			//			public CElement toMove = null;
			Point2D lastPoint = null;

			//State S0 Boomerang state machine
			public State startS0 = new State() {

				Transition clickOnIcon = new PressOnTag("draggable", BUTTON1, ">> dragS1") {
					public void action() {
						commomElement = getShape();
						commomElement.aboveAll();
						//						toMove.addGhost();
						//						((CShape)toMove).getGhost().addTag("draggable");
						lastPoint = getPoint();
					}
				};

				Transition clickOnMark = new PressOnTag("away", BUTTON1, ">> startS0") {
					public void action() {
						//toMove = commomElement;
					}
				}; 

				Transition clickOnCanvas = new Press(BUTTON1, ">> dragS1") {
					public void action() {
						commomElement = PhotoManagerWindow.canvas;
						lastPoint = getPoint();
					}
				};

				Transition clickOnIconToDisplay = new ClickOnTag("draggable", BUTTON1, ">> startS0") {
					public void action() {
						PhotoManagerWindow.DisplayPicture(commomElement);
					}
				};

				Transition nextImage = new KeyType('h', ">> startS0"){
					public void action(){
						//						System.out.println(gridIndexDiplayedImage);
						if(fileSystem.gridPoints.size() > 0 && gridIndexDiplayedImage < fileSystem.gridPoints.size()-1){
							gridIndexDiplayedImage++;
							CElement imageToDisplay = fileSystem.gridPoints.get(gridIndexDiplayedImage).imageAttached.visualization;
							PhotoManagerWindow.DisplayPicture(imageToDisplay);
						}
					}
				};

				Transition previousImage = new KeyType('g', ">> startS0"){//G
					public void action(){
						//						System.out.println(gridIndexDiplayedImage);
						if(fileSystem.gridPoints.size() > 0 && gridIndexDiplayedImage > 0){
							gridIndexDiplayedImage--;
							CElement imageToDisplay = fileSystem.gridPoints.get(gridIndexDiplayedImage).imageAttached.visualization;
							PhotoManagerWindow.DisplayPicture(imageToDisplay);
						}
					}
				};

				public void enter(){ }

				Transition catchItem = new Event("catchItem", ">> dragS1"){
					public void action(){
						//						commonElement = commomElement;
						commomElement.aboveAll();
						lastPoint = new Point2D.Double(PhotoManagerWindow.canvas.getMousePosition().x, PhotoManagerWindow.canvas.getMousePosition().y);
					}
				};

			};

			//State S1 - Dragging an object
			public State dragS1 = new State() {

				public void enter() {
					commomElement.removeTag("notAttachable");
					if(PhotoManagerWindow.canvas.getMousePosition() != null){
						lastX = PhotoManagerWindow.canvas.getMousePosition().x;
						lastY = PhotoManagerWindow.canvas.getMousePosition().y;						
					}else{
						lastX = (int)commomElement.getCenterX();
						lastY = (int)commomElement.getCenterY();
					}
					mouseSpeed = 0;
					tick = System.currentTimeMillis();
					commomElement.setOutlinePaint(Color.RED);
				}

				Transition releaseIcon = new Release(BUTTON1, ">> startS0") {
					public void action(){
						commomElement.translateBy(lastPoint.getX() - getPoint().getX(), lastPoint.getY() - getPoint().getY());
						//						toMove.removeGhost();
					}
				};				

				Transition dragWithSpeed = new DragOnTag("draggable", BUTTON1, ">> dragS2") {
					public void action() {
						commomElement.translateBy(getPoint().getX() - lastPoint.getX(), getPoint().getY() - lastPoint.getY());
						lastPoint = getPoint();
						mouseSpeed = calculateMouseSpeed((int)lastPoint.getX(),(int)lastPoint.getY(), System.currentTimeMillis());
					}
					public boolean guard(){
						return mouseSpeed > MIN_SPEED && !commomElement.equals(PhotoManagerWindow.canvas);
					}
				};

				Transition drag = new DragOnTag("draggable", BUTTON1) {
					public void action() {
						commomElement.translateBy(getPoint().getX() - lastPoint.getX(), getPoint().getY() - lastPoint.getY());
						lastPoint = getPoint();
						mouseSpeed = calculateMouseSpeed((int)lastPoint.getX(),(int)lastPoint.getY(), System.currentTimeMillis());
					}
					public boolean guard(){
						return !commomElement.equals(PhotoManagerWindow.canvas);
					}
				};

				Transition scrollCanvas = new Drag(BUTTON1) {
					public void action() {						
						double translation = getPoint().getY() - lastPoint.getY();

						CElement firstGridNode = fileSystem.gridPoints.get(0).vis;
						CElement lastGridNode = fileSystem.gridPoints.get(fileSystem.gridPoints.size()-1).vis;
						if(firstGridNode.getCenterY() + translation > fileSystem.initialY 
								||
								lastGridNode.getCenterY() + translation < fileSystem.initialY){
							translation = 0;
						}

						commomElement.translateBy(0, translation);
						lastPoint = getPoint();

						//to keep the ghosts at the same position
						if(!itensGhosts.isEmpty()){
							Iterator it = itensGhosts.entrySet().iterator();
							while (it.hasNext()) {
								Map.Entry pairs = (Map.Entry)it.next();
								ItemGhost ghost = (ItemGhost)pairs.getValue();
								itensAway.get(ghost.parentId).visualization.translateBy(0, -(translation));
								ghost.visualization.translateBy(0, -(translation));
							}
						}
						//mouseSpeed = calculateMouseSpeed((int)lastPoint.getX(),(int)lastPoint.getY(), System.currentTimeMillis());
					}
				};

				Transition accidentalyLeaveObject = new LeaveOnTag("draggable", ">> dragS1"){
					public void action(){
						if(commomElement != null && !commomElement.equals(PhotoManagerWindow.canvas) && PhotoManagerWindow.canvas.getMousePosition() != null){
							commomElement.translateTo(PhotoManagerWindow.canvas.getMousePosition().x, PhotoManagerWindow.canvas.getMousePosition().y);
						}
					}
				};

				public void leave() {
					commomElement.setOutlinePaint(Color.BLACK);
				}
			};

			//State S2 - dragging an object with enough speed to throw it
			public State dragS2 = new State(){

				public void enter(){
					commomElement.addTag("notAttachable");
					commomElement.setOutlinePaint(Color.GREEN);
					//					armTimer(25, false);
					//					System.out.println("aqui");
				}


				Transition throwIcon = new Release(BUTTON1, ">> startS0") {
					public void action(){
						//thrown away animation
						GeometricVector vec = new GeometricVector(lastX - secondLastX, lastY - secondLastY);
						ItemAway itemAway = new ItemAway(commomElement, vec);
						itensAway.put(commomElement.hashCode(), itemAway);

						Animation anim = new AnimationRotateBy(15) {
							//get the vector out of the mouse movement
							public double deltaX = lastX - secondLastX;
							public double deltaY = lastY - secondLastY;
							@Override
							public void step(double t) {

								//normalize the vector to lenght 1
								double vectorLength = Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));								
								deltaX = deltaX / vectorLength;
								deltaY = deltaY / vectorLength;

								//scale the vector to determine the speed of the animation
								deltaX *= 50;
								deltaY *= 50;

								getAnimated().translateBy(deltaX, deltaY);								
								getAnimated().rotateBy(t);
								setLapDuration(200);

								//stop and disappear when off the screen
								if(getAnimated().getCenterX() < 0 || getAnimated().getCenterY() < 0
										|| getAnimated().getCenterX() > WIDTH || getAnimated().getCenterY() > HEIGHT){
									this.stop();
									getAnimated().setTransparencyFill(0);
									getAnimated().setTransparencyOutline(0);
								}
							}
						};

						itemAway.setAnimation(anim);
						commomElement.animate(anim);						
						commomElement.removeTag("draggable");//remove draggable to deactivate click while the animation goes
						//						toMove.removeGhost();

						//creat mark to indicate the thrown object
						ItemGhost itemMark = new ItemGhost(commomElement.hashCode(), PhotoManagerWindow.canvas.newEllipse(lastX, lastY, 25, 25));
						itemMark.visualization.setTransparencyFill(0.2f);

						Animation animTakeItemBack = new AnimationScaleTo(1.2, 1.2);						
						animTakeItemBack.setNbLaps(Animation.INFINITE_NUMBER_OF_LAPS);
						animTakeItemBack.setLapDuration(300);
						animTakeItemBack.setDelay(20);

						itemMark.visualization.animate(animTakeItemBack);
						itemMark.setAnimation(animTakeItemBack);
						itemMark.visualization.addTag("away");

						itensGhosts.put(itemMark.visualization.hashCode(), itemMark);
					}
				};

				Transition dragWithoutSpeed = new Drag(BUTTON1) {
					public void action() {
						commomElement.translateBy(getPoint().getX() - lastPoint.getX(), getPoint().getY() - lastPoint.getY());
						lastPoint = getPoint();
						mouseSpeed = calculateMouseSpeed((int)lastPoint.getX(),(int)lastPoint.getY(), System.currentTimeMillis());
						armTimer(40, false);
					}
					public boolean guard(){
						return mouseSpeed <= MIN_SPEED && !commomElement.equals(PhotoManagerWindow.canvas);
					}
				};

				Transition dragWithSpeed = new Drag(BUTTON1, ">> dragS2") {
					public void action() {
						commomElement.translateBy(getPoint().getX() - lastPoint.getX(), getPoint().getY() - lastPoint.getY());
						lastPoint = getPoint();
						mouseSpeed = calculateMouseSpeed((int)lastPoint.getX(),(int)lastPoint.getY(), System.currentTimeMillis());
					}

					public boolean guard(){
						return mouseSpeed > MIN_SPEED && !commomElement.equals(PhotoManagerWindow.canvas);
					}
				};

				Transition moveBackToS1 = new TimeOut(">> dragS1"){
					public void action(){
						//mouseSpeed = calculateMouseSpeed(lastX,lastY, System.currentTimeMillis());
					}
				};

				Transition accidentalyLeaveObject = new LeaveOnTag("draggable", ">> dragS2"){
					//TODO: conflict when passing in front of other object
					public void action(){
						if(commomElement != null && PhotoManagerWindow.canvas.getMousePosition() != null){
							commomElement.translateTo(PhotoManagerWindow.canvas.getMousePosition().x, PhotoManagerWindow.canvas.getMousePosition().y);
						}
					}
				};

				public void leave() {
					commomElement.setOutlinePaint(Color.BLACK);					
				}
			};

		};

		catchInteraction = new CStateMachine(){


			CElement awayCircle = null;
			Point2D lastPoint = null;

			//State S0 Boomerang state machine
			public State startS0 = new State() {

				Transition enterOnMark = new EnterOnTag("away", ">> previewS1") {
					public void action() {
						awayCircle = getShape();
						//lastPoint = getPoint();
					}
				};

			};

			public State previewS1 = new State() {

				public void enter(){					
					final ItemGhost ghost = itensGhosts.get(awayCircle.hashCode());
					final ItemAway itemCaught = itensAway.get(ghost.parentId);
					CElement vis = itemCaught.visualization;

					itemCaught.animation.stop();

					Animation animCatch = new AnimationRotateBy(15){
						GeometricVector motionVector = new GeometricVector((int)(ghost.visualization.getCenterX() - itemCaught.visualization.getCenterX()),
								(int)(ghost.visualization.getCenterY() - itemCaught.visualization.getCenterY()));

						@Override
						public void step(double t) {
							//to make the object reappear and come back with the same direction
							getAnimated().translateBy(motionVector.X*50, motionVector.Y*50);
							getAnimated().setTransparencyFill(1);
							getAnimated().setTransparencyOutline(1);
							getAnimated().rotateBy(t);
							setLapDuration(200);

							if(getAnimated().intersects(ghost.visualization) != null){
								getAnimated().translateTo(ghost.visualization.getCenterX(), ghost.visualization.getCenterY());
								this.stop();
								getAnimated().rotateTo(0);//to straighten the object
							}
						}
					};

					itemCaught.setAnimation(animCatch);
					vis.animate(animCatch);

				}

				Transition leaveMark = new LeaveOnTag("away", ">> startS0"){
					public void action(){
						final ItemGhost ghost = itensGhosts.get(awayCircle.hashCode());
						final ItemAway itemCaught = itensAway.get(ghost.parentId);
						CElement vis = itemCaught.visualization;

						if(Math.abs(itemCaught.visualization.getCenterX() - ghost.visualization.getCenterX()) > 20
								&& Math.abs(itemCaught.visualization.getCenterX() - ghost.visualization.getCenterX()) > 20){
							itemCaught.animation.stop();
						}

						Animation animRethrow = new AnimationRotateBy(15){
							GeometricVector motionVector = itemCaught.motionVector;							

							@Override
							public void step(double t) {								
								getAnimated().translateBy(motionVector.X*50, motionVector.Y*50);								
								getAnimated().rotateBy(t);
								setLapDuration(200);

								//stop and disappear when off the screen
								if(getAnimated().getCenterX() < 0 || getAnimated().getCenterY() < 0
										|| getAnimated().getCenterX() > WIDTH || getAnimated().getCenterY() > HEIGHT){
									getAnimated().setTransparencyFill(0);
									getAnimated().setTransparencyOutline(0);
									this.stop();
								}
							}
						};
						itemCaught.setAnimation(animRethrow);
						vis.animate(animRethrow);
					}
				};

				Transition clickMark = new PressOnTag("away", BUTTON1, ">> startS0"){
					public void action(){						
						ItemGhost ghost = itensGhosts.get(awayCircle.hashCode());
						ItemAway item = itensAway.get(ghost.parentId);

						if(Math.abs(item.visualization.getCenterX() - ghost.visualization.getCenterX()) < 5
								&& Math.abs(item.visualization.getCenterY() - ghost.visualization.getCenterY()) < 5){

							item.visualization.addTag("draggable");
							itensGhosts.get(awayCircle.hashCode()).visualization.remove();
							itensGhosts.get(awayCircle.hashCode()).animation.stop();
							itensGhosts.get(awayCircle.hashCode()).deleteAnimation();

							itensGhosts.remove(awayCircle.hashCode());
							commomElement = item.visualization;
							fireEvent(new VirtualEvent("catchItem"));
							fireEvent(new VirtualEvent("catchItem2"));
						}
					}
				};

			};
		};

		gridSnapInteraction = new CStateMachine(){
			GridNode gridPoint;
			//			CElement commomElement = null;
			int lastIndex;

			State idleGrid = new State() {

				public void enter(){
					//gridPoint = null;
				}

				Transition catchEvent = new Event("catchItem2", ">> snapGrid"){

					public void action(){
						gridIndexDraggingObject = gridPoint.index;
						gridIndexDiplayedImage = gridPoint.index;
						System.out.println(gridPoint.index);
						gridPoint.highlight();
					}

					public boolean guard(){
						//						commomElement = commomElement;
						System.out.println("aqui");
						if(commomElement != null){
							Point2D currentPoint = new Point2D.Double(commomElement.getCenterX(), commomElement.getCenterY());
							System.out.println(currentPoint);
							gridPoint = fileSystem.closestPoint(currentPoint);

							if(gridPoint == null)
								return false;
							else
								return true;
						}else{
							return false;
						}

					}

				};

				Transition closeToGrid = new PressOnTag("draggable", BUTTON1, ">> snapGrid") {

					public void action() {
						//commomElement = getShape();
						gridIndexDraggingObject = gridPoint.index;
						gridIndexDiplayedImage = gridPoint.index;
						gridPoint.highlight();
						//						System.out.println("to highlight: "+gridPoint.index);
					}
					public boolean guard(){
						commomElement = getShape();
						System.out.println(commomElement.hashCode());
						if(commomElement != null){
							Point2D actualPoint = new Point2D.Double(commomElement.getCenterX(), commomElement.getCenterY());
							gridPoint = fileSystem.closestPoint(actualPoint);

							if(gridPoint == null)
								return false;
							else
								return true;
						}else{
							return false;
						}
					}
				};
			};


			State snapGrid = new State(){

				Transition snapToGrid = new Release(BUTTON1, ">> idleGrid"){
					public void action(){
						if(gridPoint != null){
							fileSystem.snapObject(gridIndexDraggingObject, gridPoint.index);
							//							toMove.translateTo(gridPoint.vis.getCenterX(), gridPoint.vis.getCenterY());
							gridPoint.unhighlight();
						}
					}
					public boolean guard(){
						return mouseSpeed < MIN_SPEED && !commomElement.hasTag("notAttachable");
					}
				};


				//				Transition noPoint = new DragOnTag("draggable", BUTTON1, ">> idleGrid") {
				//					GridNode newGridPoint;
				//
				//					public void action() {
				//						gridPoint.unhighlight();						
				//					}
				//
				//					public boolean guard(){
				//						Point2D actualPoint = getPoint();
				//						newGridPoint = fileSystem.closestPoint(actualPoint);
				//
				//						if(newGridPoint == null)
				//							return true;
				//
				//						return false;
				//					}
				//				};

				//				Transition enterGridNode = new EnterOnTag("gridNode", ">> snapGrid"){
				//					CShape newGridPointShape;
				//					GridNode newGridPoint;
				//					
				//					public void action(){
				//						gridPoint.highlight();
				//					}
				//					
				//					public boolean guard(){
				//						newGridPointShape = getShape();
				//						newGridPoint = fileSystem.closestPoint(new Point2D.Double(newGridPointShape.getCenterX(),newGridPointShape.getCenterY()));
				//						
				//						if(newGridPoint != null && !newGridPoint.equals(gridPoint)){
				//							gridPoint.unhighlight();
				//							gridPoint = newGridPoint;
				//							return true;
				//						}						
				//						return false;
				//					}
				//					
				//				};

				Transition closeToOtherPoint = new DragOnTag("draggable", BUTTON1, ">> snapGrid") {
					GridNode newGridPoint;

					public void action() {
						//						System.out.println("highlight: "+gridPoint.index);
						gridPoint.highlight();						
					}

					public boolean guard(){
						//						System.out.println("aqui");
						Point2D actualPoint = new Point2D.Double(commomElement.getCenterX(), commomElement.getCenterY());
						newGridPoint = fileSystem.closestPoint(actualPoint);						

						if(newGridPoint != null){
							gridPoint.unhighlight();
							gridPoint = newGridPoint;
							return true;
						}						
						return false;
					}
				};

			};
		};
	}




	protected double calculateMouseSpeed(int newX, int newY, long newtick) {

		if(lastX == -1)  {
			lastX = newX;
			lastY = newY;
			tick = newtick;
			return 0;
		}

		long time = newtick - tick;
		time = time != 0 ? time : time + 1; //to avoid division per zero

		double distance = Math.sqrt(Math.pow(newX-lastX, 2)
				+
				Math.pow(newY-lastY, 2));
		double speed = distance/time;

		//		System.out.println("lastX: " +lastX + " newX: "+newX);
		//		System.out.println("lastY: " +lastY + " newY: "+newX);
		//		System.out.println("distance: "+ distance);
		//		System.out.println("speed: "+ mouseSpeed);

		secondLastX = lastX;
		secondLastY = lastY;
		lastX = newX;
		lastY = newY;
		tick = newtick;		

		//		if (distance > 20) return 0; //if the user is just trying to move the object for a distance location
		return speed;
	}	


}
